package com.zyl.file.websocket;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.handler.codec.serialization.ObjectEncoder;
import io.netty.handler.stream.ChunkedWriteHandler;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;
import java.net.InetSocketAddress;
import java.util.Objects;

/**
 * @Author zyl
 */
@Slf4j
@Component
public class WebSocketServer {

    /**
     * webSocket协议名
     */
    private static final String WEBSOCKET_PROTOCOL = "WebSocket";

    /**
     * 端口号
     */
    private int port = 8888;

    /**
     * webSocket路径
     */
    private String webSocketPath = "/webSocket";

    @Resource
    private WebSocketFrameHandler webSocketHandler;

    private EventLoopGroup bossGroup;
    private EventLoopGroup workGroup;

    /**
     * 启动
     */
    @SneakyThrows
    private void start() {
        bossGroup = new NioEventLoopGroup();
        workGroup = new NioEventLoopGroup();
        ServerBootstrap bootstrap = new ServerBootstrap();
        // bossGroup辅助客户端的tcp连接请求，workGroup负责与客户端之前的读写操作
        bootstrap.group(bossGroup, workGroup);
        // 设置NIO类型的channel
        bootstrap.channel(NioServerSocketChannel.class);
        // 设置监听端口
        bootstrap.localAddress(new InetSocketAddress(port));
        // 连接达到时会创建一个通道
        bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
            @Override
            protected void initChannel(SocketChannel ch) {
                // 流水线管理通道中的处理程序（Handle），用来处理业务
                // webSocket协议本身是基于http协议的，所以这边也要使用http编解码器
                ch.pipeline().addLast(new HttpServerCodec());
                ch.pipeline().addLast(new ObjectEncoder());
                // 以块的方式来写的处理器
                ch.pipeline().addLast(new ChunkedWriteHandler());
                /*
                  1.http数据在传输过程中是分段的，HttpObjectAggregator可以将多个段聚合
                  2.这就是为什么，当浏览器发送大量数据时，就会发送多次http请求
                 */
                ch.pipeline().addLast(new HttpObjectAggregator(8192));
                /*
                  1.对应websocket，它的数据是以帧（frame）的形式传递
                  2.浏览器请求时 ws://localhost:58080/xxx 表示请求的url
                  3.核心功能是将http协议升级为ws协议，保持长连接
                 */
                ch.pipeline().addLast(new WebSocketServerProtocolHandler(webSocketPath, WEBSOCKET_PROTOCOL, Boolean.TRUE, 65536 * 10));
                // 自定义handler，处理业务逻辑
                ch.pipeline().addLast(webSocketHandler);
            }
        });
        // 配置完成，开始绑定server，通过调用sync同步方法阻塞知道绑定成功
        ChannelFuture channelFuture = bootstrap.bind().sync();
        log.info("Netty started on port: {} ", this.port);
        // 对关闭通道进行监听
        channelFuture.channel().closeFuture().sync();
    }

    /**
     *
     */
    @SneakyThrows
    @PreDestroy
    public void destroy() {
        if (Objects.nonNull(bossGroup)) {
            bossGroup.shutdownGracefully().sync();
        }
        if (Objects.nonNull(workGroup)) {
            workGroup.shutdownGracefully().sync();
        }
    }

    /**
     *
     */
    @PostConstruct
    public void init() {
        // 需要开启一个新的线程来执行Netty server 服务器
        new Thread(this::start).start();
    }

}
